package scratch.communicator.main

def addressesInLan = ['10.1.1.4','10.1.1.16','10.1.1.14','10.1.1.6']

new Tangle(
	addresses: addressesInLan,
	messageHandler: new MessageHandler()
).run()

class ScratchConnection {
	Socket socket
	MessageHandler messageHandler
	String name
	
	def blockingListen() {
			InputStream input = socket.getInputStream()
			messageHandler.handleMessage(blockingScratchMessageRead(input), name)
	}
	
	def reconnect() {
		socket = new Socket(name, 42001)
	}
	
	def write(String message) {
		Integer l = message.length()
		OutputStream output = socket.getOutputStream()
		for (int i=3; i>=0; i--) {
			output.write((l >> (8*i)) & 0xFF)
		}
		output << message
	}
	
	def blockingScratchMessageRead(InputStream input) {
		def size = 0
		for (int i=0; i<4; i++) {
			def _byte = input.read()
			size += _byte
			size << 1
		}
		
		def messageBlock = []
		for (int i=0; i<size; i++) {
			def _byte = input.read()
			messageBlock << (_byte as char)
		}
		
		return messageBlock.join('')
	}
}

class MessageHandler {
	Map connections
	def handleMessage(String message, String sourceName) {
		synchronized (connections) {
			connections.each { String destinationName, ScratchConnection destination ->
				if (destinationName != sourceName) {
					println "[${message}] from ${sourceName} to ${destinationName}"
					try {
						destination.write(message)
					}
					catch (Exception e1) {
						// Try one more time
						try {
							destination.reconnect()
							destination.write(message)
						}
						catch (Exception e2){
							println "${destinationName} not responding"
						}
					}
				}
			}
		}
	}
}

class Tangle {
	def addresses
	def connections = [:]
	MessageHandler messageHandler
	
	def run() {
		def threads = []
		messageHandler.connections = connections
		addresses.each { address ->
			threads << Thread.start {
				try {
					
					def connection = new ScratchConnection(
						socket: new Socket(address, 42001),
						messageHandler: messageHandler,
						name: address
					)
					synchronized (connections) {
						connections[address] = connection 
					}
					
					while(true) {
						connection.blockingListen()
					}
					
				}
				catch (Exception e) {
					println "Connection failed ${address}"
				}
			}
		}
		threads.each { Thread thread -> thread.join() }
	}
	
}